﻿/*============================================================================
  File:    AMOBrowser.cs

  Summary: Implements a sample AMO browser utility in C#.

  Date:    June 13, 2005
------------------------------------------------------------------------------
  This file is part of the Microsoft SQL Server Code Samples.

  Copyright (C) Microsoft Corporation.  All rights reserved.

  This source code is intended only as a supplement to Microsoft
  Development Tools and/or on-line documentation.  See these other
  materials for detailed information regarding Microsoft code samples.

  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  PARTICULAR PURPOSE.
============================================================================*/

#region Using directives

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

using Microsoft.SqlServer.MessageBox;
using Microsoft.AnalysisServices;

#endregion

namespace Microsoft.Samples.SqlServer
{
    /// <remarks>
    /// The main functionality of this class is to fill a TreeView 
    /// with TreeNodes associated with AMO objects.
    /// The filling of the TreeView is incremental: as user 
    /// expands TreeNodes, we'll create the TreeNode children
    /// (we'll populate the TreeView on-demand).
    /// When user selects a TreeNode, the properties of the 
    /// associated AMO object will be displayed in a PropertyGrid control.
    /// </remarks>
    partial class AmoBrowser : Form
    {
        // used for loading on demand of tree nodes content
        private static string TagForLoadingNodes = "LOADING";

        // the AMO Server that we'll use to connect to Analysis Services 
        // engine to get the metadata
        private Server server;

        public AmoBrowser()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Called when the 'Close' button is pressed, this method closes 
        /// this form (and ultimately the application).
        /// </summary>
        private void closeButton_Click(object sender, EventArgs e)
        {
            try
            {
                if (null != this.server)
                {
                    this.server.Disconnect();
                }
            }
            finally
            {
                this.Close();
            }
        }

        /// <summary>
        /// Called when the 'Connect' button is pressed, this method connects 
        /// to Analysis Services engine and populates the tree view with the 
        /// initial nodes associated with the AMO Server object.
        /// </summary>
        private void connectButton_Click(object sender, EventArgs e)
        {
            try
            {
                this.Enabled = false;

                //--------------------------------------------------------------------------------
                // Cleanup
                //--------------------------------------------------------------------------------
                this.treeView.Nodes.Clear();
                this.propertyGrid.SelectedObject = null;
                if (null != this.server)
                {
                    this.server.Disconnect();
                }
                else
                {
                    this.server = new Server();
                }

                //--------------------------------------------------------------------------------
                // Connect to Analysis Services server and create tree nodes for Server object.
                //--------------------------------------------------------------------------------
                this.server.Connect(this.connectionStringTextBox.Text);
                CreateNode(this.treeView.Nodes, this.server.Name, this.server);
            }
            catch (AmoException exception)
            {
                ExceptionMessageBox emb = new ExceptionMessageBox(exception);
                emb.Show(this);
            }
            finally
            {
                this.Enabled = true;
            }
        }

        /// <summary>
        /// When a TreeNode is expanded, this method creates new TreeNodes 
        /// children (associated with AMO objects).
        /// This method is the core of create-on-demand of TreeNodes while 
        /// user expands nodes in the TreeView.
        /// </summary>
        private void treeView_AfterExpand(object sender, TreeViewEventArgs e)
        {
            //--------------------------------------------------------------------------------
            // Some validations.
            //--------------------------------------------------------------------------------
            if (null == e)
            {
                return;
            }

            TreeNode node = e.Node;
            if (null == node)
            {
                return;
            }

            //--------------------------------------------------------------------------------
            // If the TreeNode being expanded has the information already loaded, we'll do nothing.
            //
            // REMINDER: we are using a special child TreeNode with Tag = "LOADING" to mark
            //           TreeNodes that don't have their children loaded.
            //--------------------------------------------------------------------------------
            TreeNodeCollection nodes = node.Nodes;
            if ((nodes.Count != 1)
                || (nodes[0].Tag != (object)TagForLoadingNodes))
            {
                return;
            }

            //--------------------------------------------------------------------------------
            // The current TreeNode (being expanded) will get his children populated. 
            // This TreeNode can be associated with either:
            // - an AMO collection: in which case we'll create a child TreeNode for each collection item
            //  OR
            // - an AMO object: in which case we'll create a child TreeNode for each collection property
            //--------------------------------------------------------------------------------
            nodes.Clear();
            if (node.Tag is ICollection)
            {
                CreateNodesForCollectionItems(nodes, (ICollection)node.Tag);
            }
            else
            {
                CreateNodesForCollectionProperties(nodes, node.Tag);
            }
        }

        /// <summary>
        /// Puts into PropertyGrid the AMO object associated with the selected 
        /// TreeNode (the association is being done with the TreeNode.Tag 
        /// property).
        /// </summary>
        private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            this.propertyGrid.SelectedObject = (null != e)
                && (null != e.Node)
                ? e.Node.Tag : null;
        }

        /// <summary>
        /// Creates a TreeNode with a TreeNode child. The TreeNode child is 
        /// used to implement load-on-demand of nodes / AMO objects.
        /// </summary>
        /// <param name="nodes">The collection of TreeNodes where the newly 
        /// created TreeNode will be added.</param>
        /// <param name="text">The text of the TreeNode to created.</param>
        /// <param name="obj">The AMO object that is associated with the 
        /// TreeNode. The association is done with the TreeNode.Tag property.</param>
        private static void CreateNode(TreeNodeCollection nodes, string text, object obj)
        {
            TreeNode node = nodes.Add(text);
            node.Tag = obj;
            node.Nodes.Add("... loading ...").Tag = TagForLoadingNodes;
        }

        /// <summary>
        /// Creates nodes in the TreeView for the collection properties of the specified object.
        /// </summary>
        /// <param name="nodes">The collection of TreeNodes where the newly 
        /// created nodes will be added.</param>
        /// <param name="obj">The object for which we'll create TreeNodes for 
        /// collection properties.</param>
        private static void CreateNodesForCollectionProperties(TreeNodeCollection nodes, object obj)
        {
            if ((null == nodes)
                || (null == obj))
            {
                return;
            }

            //--------------------------------------------------------------------------------
            // We need to create a TreeNode for each collection property of the AMO object.
            // We'll first get all the public and non-static properties, then we'll filter per type.
            //--------------------------------------------------------------------------------
            PropertyInfo[] properties 
                = obj.GetType().GetProperties(BindingFlags.Instance 
                | BindingFlags.Public);

            foreach (PropertyInfo property in properties)
            {
                // We are also skipping a special collection property of many 
                // AMO objects: the 'OwningCollection'.
                if ((property.Name != "OwningCollection")
                    && typeof(ICollection).IsAssignableFrom(property.PropertyType))
                {
                    CreateNode(nodes, property.Name, property.GetValue(obj, null));
                }
            }
        }

        /// <summary>
        /// Creates nodes in the TreeView for each item in the specified collection.
        /// </summary>
        /// <param name="nodes">The collection of TreeNodes where the newly 
        /// created nodes will be added.</param>
        /// <param name="collection">The collection for which we'll create 
        /// TreeNodes.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        private static void CreateNodesForCollectionItems(TreeNodeCollection nodes, ICollection collection)
        {
            if ((null == nodes)
                || (null == collection)
                || (collection.Count == 0))
            {
                return;
            }

            //--------------------------------------------------------------------------------
            // We could just create a TreeNode for each item in the collection. But, in order
            // to have a better looking TreeView, we'll try to use the items names as much as 
            // possible. Thus, we'll have custom code for known AMO types.
            //--------------------------------------------------------------------------------
            if (collection is NamedComponentCollection)
            {
                // 'NamedComponent' objects have Name and ID properties, this 
                // is the base class for many AMO classes
                // like Database, Dimension, Cube, MiningStructure.
                foreach (NamedComponent obj in collection)
                {
                    CreateNode(nodes, obj.Name, obj);
                }
            }
            else if (collection is ModelComponentCollection)
            {
                // 'ModelComponent' objects don't necessarily have a Name and 
                // ID of their own, this is the base class for AMO objects 
                // like CubeAttribute, PerspectiveMeasure or AggregationDesignDimension.
                foreach (ModelComponent obj in collection)
                {
                    CreateNode(nodes, obj.ToString(), obj);
                }
            }
            else if (collection is AnnotationCollection)
            {
                // Special case: 'Annotation' objects
                foreach (Annotation obj in collection)
                {
                    CreateNode(nodes, obj.Name, obj);
                }
            }
            else if (collection is AlgorithmParameterCollection)
            {
                // Special case: 'AlgorithmParameter' objects
                foreach (AlgorithmParameter obj in collection)
                {
                    CreateNode(nodes, obj.Name, obj);
                }
            }
            else if (collection is ReportParameterCollection)
            {
                // Special case: 'ReportParameter' objects
                foreach (ReportParameter obj in collection)
                {
                    CreateNode(nodes, obj.Name, obj);
                }
            }
            else if (collection is TraceColumnCollection)
            {
                // Special case: 'TraceColumn' items
                foreach (TraceColumn obj in collection)
                {
                    CreateNode(nodes, obj.ToString(), obj);
                }
            }
            else if (collection is TraceEventCollection)
            {
                // Special case: 'TraceEvent' objects
                foreach (TraceEvent obj in collection)
                {
                    CreateNode(nodes, obj.EventID.ToString(), obj);
                }
            }
            else if (collection is ServerPropertyCollection)
            {
                // Special case: 'ServerProperty' objects
                foreach (ServerProperty obj in collection)
                {
                    CreateNode(nodes, obj.Name, obj);
                }
            }
            else if (collection is IList)
            {
                // Other collections not handled above, like DataItemCollection 
                // or BindingCollection.
                IList tmp = (IList)collection;

                for (int i = 0, n = tmp.Count; i < n; ++i)
                {
                    CreateNode(nodes, "#" 
                        + i.ToString(CultureInfo.CurrentUICulture), tmp[i]);
                }
            }
            else
            {
                // In case we missed to handle an AMO collection, we'll keep 
                // this reminder.
                System.Diagnostics.Debug.Assert(false, "Missing code.");
            }
        }
    }
}
